/*
	This file is part of PropExtractor.

	PropExtractor is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	PropExtractor is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with PropExtractor. If not, see <http://www.gnu.org/licenses/>.
*/

#include "PEIncludes.h"

#include "PEAliceSoftArchiveAfa.h"

#include "PEFile.h"
#include "PETools.h"
#include "PEArchive.h"
#include "PEImage.h"

#include "PEAliceSoftImageQNT.h"

namespace pe
{

	int AliceArchiveAfaReadHeader(AliceArchiveAfaHeader* a_Target, PEStream* a_Stream)
	{
		PEStreamRead(a_Stream, a_Target, sizeof(AliceArchiveAfaHeader), 1);

		if (
			a_Target->header[0] != 'A' ||
			a_Target->header[1] != 'F' ||
			a_Target->header[2] != 'A' ||
			a_Target->header[3] != 'H'
		)
		{
			return -1;
		}

		return PE_OK;
	}

	int AliceArchiveAfaReadData(AliceArchiveAfa* a_Target, AliceArchiveAfaHeader* a_Header, PEStream* a_Stream)
	{
		a_Target->header = new AliceArchiveAfaHeader;
		memcpy(a_Target->header, a_Header, sizeof(AliceArchiveAfaHeader));

		/*PE_STRCLRCPY(a_Target->path, a_Stream->path);

		char* lastslash = strrchr((char*)a_Stream->path, '\\');
		if (!lastslash)
		{
			lastslash = strrchr((char*)a_Stream->path, '/');
		}
		a_Target->workingdir = new char[lastslash - a_Stream->path + 1];
		strncpy(a_Target->workingdir, a_Stream->path, lastslash - a_Stream->path + 1);
		a_Target->workingdir[lastslash - a_Stream->path + 1] = 0;*/

		PE_WSTRCLRCPY(a_Target->path, a_Stream->path);
		PEStreamGetDir(a_Stream, &a_Target->workingdir);

		a_Target->info = new AliceArchiveAfaFileInfo;
		a_Target->info->header = new AliceArchiveAfaFileInfoHeader;
		PEStreamRead(a_Stream, a_Target->info->header, sizeof(AliceArchiveAfaFileInfoHeader), 1);

		byte* data = new byte[a_Target->info->header->uncompressed_size];

		z_stream zlib_stream;
		zlib_stream.zalloc = Z_NULL;
		zlib_stream.zfree = Z_NULL;
		zlib_stream.opaque = Z_NULL;
		zlib_stream.avail_in = 0;
		zlib_stream.next_in = Z_NULL;

		inflateInit(&zlib_stream);

		byte* dst = data;
		unsigned int have;
		int ret;
		unsigned char in[CHUNK];
		unsigned char out[CHUNK];
		unsigned int total_compressed = 0;
		unsigned int total_read = 0;

		unsigned int curr_pos;
		PEStreamGetCurrentPos(a_Stream, &curr_pos);

		do 
		{
			if (PEStreamRead(a_Stream, in, 1, CHUNK) != PE_OK)
			{
				(void)inflateEnd(&zlib_stream);
				return Z_ERRNO;
			}

			PEStreamGetBytesRead(a_Stream, &zlib_stream.avail_in);

			if (zlib_stream.avail_in == 0) 
			{ 
				break; 
			}
			zlib_stream.next_in = in;

			total_compressed += zlib_stream.avail_in;

			do 
			{
				zlib_stream.avail_out = CHUNK;
				zlib_stream.next_out = out;
				ret = inflate(&zlib_stream, Z_NO_FLUSH);

				switch (ret) 
				{
				case Z_NEED_DICT:
					ret = Z_DATA_ERROR;					

				case Z_DATA_ERROR:
				case Z_MEM_ERROR:
					(void)inflateEnd(&zlib_stream);
					return ret;
				}

				have = CHUNK - zlib_stream.avail_out;
				total_read += have;
				memcpy(dst, out, have);
				dst += have;
			}
			while (zlib_stream.avail_out == 0);
		} 
		while (ret != Z_STREAM_END);

		byte* src = data;

		a_Target->info->entries_total = a_Target->info->header->files_total;
		a_Target->info->entries = new AliceArchiveAfaEntry[a_Target->info->entries_total];

		for (unsigned int i = 0; i < a_Target->info->header->files_total; i++)
		{
			src += ReadBytes(&a_Target->info->entries[i].path_len, 4, 1, src);
			src += ReadBytes(&a_Target->info->entries[i].unknown2, 4, 1, src);

			src += ReadUTF8String(&a_Target->info->entries[i].path, src);

			unsigned int diff = a_Target->info->entries[i].unknown2 - (a_Target->info->entries[i].path_len + 1);
			src += diff;

			src += ReadBytes(&a_Target->info->entries[i].unknown3, 4, 1, src);
			src += ReadBytes(&a_Target->info->entries[i].something, 8, 1, src);
			src += ReadBytes(&a_Target->info->entries[i].offset, 4, 1, src);
			src += ReadBytes(&a_Target->info->entries[i].unknown4, 4, 1, src);
		}

		return PE_OK;
	}

	int AliceArchiveAfaOpenFile(PEStream* a_Target, AliceArchiveAfa* a_Archive, unsigned int a_Index)
	{
		PEStreamOpen(a_Target, a_Archive->path, L"rb", false);
		PEStreamSetPath(a_Target, a_Archive->info->entries[a_Index].path);

		// ... but with an offset

		PEStreamSeek(a_Target, a_Archive->header->unknown3 + a_Archive->info->entries[a_Index].offset, SEEK_SET);

		return PE_OK;
	}

	int AliceArchiveAfaToRaw(PEArchiveRaw* a_Target, AliceArchiveAfa* a_Archive)
	{
		a_Target->original = new AliceArchiveAfa;
		memcpy(a_Target->original, a_Archive, sizeof(AliceArchiveAfa));
		a_Target->original_type = eArchiveType_AliceAfa;

		PE_WSTRCLRCPY(a_Target->path, a_Archive->path);
		PE_WSTRCLRCPY(a_Target->workingdir, a_Archive->workingdir);

		a_Target->entries_total = a_Archive->info->entries_total;
		a_Target->entries = new PEArchiveEntryRaw[a_Target->entries_total];

		for (unsigned int i = 0; i < a_Target->entries_total; i++)
		{
			PE_WSTRCLRCPY(a_Target->entries[i].path, a_Archive->info->entries[i].path);
			//PE_STRTOWIDE(a_Target->entries[i].path, a_Archive->info->entries[i].path);

			a_Target->entries[i].offset = a_Archive->header->unknown3 + a_Archive->info->entries[i].offset;
			a_Target->entries[i].filesize = a_Archive->info->entries[i].unknown4;

			wchar_t filename[_MAX_PATH];
			unsigned int filename_length;
			GetPatternedPath(filename, &filename_length, "%name%%ext%", a_Target->entries[i].path);

			PE_WSTRCLRCPY(a_Target->entries[i].filename, filename);
		}

		return PE_OK;
	}



	ArchiveAliceSoftAfa::ArchiveAliceSoftAfa() : PEArchive()
	{

	}

	ArchiveAliceSoftAfa::~ArchiveAliceSoftAfa()
	{

	}

	int ArchiveAliceSoftAfa::LoadHeader(PEStream* a_Stream)
	{
		PEStreamRead(a_Stream, &m_Header, sizeof(AliceArchiveAfaHeader), 1);

		if (m_Header.header[0] != 'A' ||
			m_Header.header[1] != 'F' ||
			m_Header.header[2] != 'A' ||
			m_Header.header[3] != 'H'
		)
		{
			return -1;
		}

		m_Stream = a_Stream;

		return PE_OK;
	}

	int ArchiveAliceSoftAfa::LoadBody()
	{
		//PE_WSTRCLRCPY(a_Target->path, a_Stream->path);
		PEStreamGetDir(m_Stream, &m_WorkingDir);
		PEStreamRead(m_Stream, &m_InfoHeader, sizeof(AliceArchiveAfaFileInfoHeader), 1);

		byte* data = new byte[m_InfoHeader.uncompressed_size];

		z_stream zlib_stream;
		zlib_stream.zalloc = Z_NULL;
		zlib_stream.zfree = Z_NULL;
		zlib_stream.opaque = Z_NULL;
		zlib_stream.avail_in = 0;
		zlib_stream.next_in = Z_NULL;

		inflateInit(&zlib_stream);

		byte* dst = data;
		unsigned int have;
		int ret;
		unsigned char in[CHUNK];
		unsigned char out[CHUNK];
		unsigned int total_compressed = 0;
		unsigned int total_read = 0;

		unsigned int curr_pos;
		PEStreamGetCurrentPos(m_Stream, &curr_pos);

		do 
		{
			if (PEStreamRead(m_Stream, in, 1, CHUNK) != PE_OK)
			{
				(void)inflateEnd(&zlib_stream);
				return Z_ERRNO;
			}

			PEStreamGetBytesRead(m_Stream, &zlib_stream.avail_in);
			if (zlib_stream.avail_in == 0) 
			{ 
				break; 
			}
			zlib_stream.next_in = in;

			total_compressed += zlib_stream.avail_in;

			do 
			{
				zlib_stream.avail_out = CHUNK;
				zlib_stream.next_out = out;
				ret = inflate(&zlib_stream, Z_NO_FLUSH);

				switch (ret) 
				{
				case Z_NEED_DICT:
					ret = Z_DATA_ERROR;					

				case Z_DATA_ERROR:
				case Z_MEM_ERROR:
					(void)inflateEnd(&zlib_stream);
					return ret;
				}

				have = CHUNK - zlib_stream.avail_out;
				total_read += have;
				memcpy(dst, out, have);
				dst += have;
			}
			while (zlib_stream.avail_out == 0);
		} 
		while (ret != Z_STREAM_END);

		byte* src = data;

		m_EntriesTotal = m_InfoHeader.files_total;
		m_Entries = new AliceArchiveAfaEntry[m_EntriesTotal];

		SetEntries(m_EntriesTotal);

		for (unsigned int i = 0; i < m_EntriesTotal; i++)
		{
			src += ReadBytes(&m_Entries[i].path_len, 4, 1, src);
			src += ReadBytes(&m_Entries[i].unknown2, 4, 1, src);

			src += ReadUTF8String(&m_Entries[i].path, src);

			unsigned int diff = m_Entries[i].unknown2 - (m_Entries[i].path_len + 1);
			src += diff;

			src += ReadBytes(&m_Entries[i].unknown3, 4, 1, src);
			src += ReadBytes(&m_Entries[i].something, 8, 1, src);
			src += ReadBytes(&m_Entries[i].offset, 4, 1, src);
			src += ReadBytes(&m_Entries[i].unknown4, 4, 1, src);

			AddEntry(m_Entries[i].path, m_Header.unknown3 + m_Entries[i].offset);
		}

		// convert to entries

		return PE_OK;
	}

}